/*
 *  Copyright 2011 yura.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */

package org.opu.vcforms.transform_t2d.var.calc;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.regex.Pattern;

/**
 *
 * @author yura
 */
public class Yylex implements Lexer {

    /** Token returned by the scanner to signal the end of its input.  */
    public final int EOF;

    /** Token number, to be returned by the scanner.  */
    public final int NUM;

    private Object lastValue = null;
    private Scanner scanner;
    private Pattern numberPattern;

    public Yylex(InputStream stream, int eof, int num) {
        EOF = eof;
        NUM = num;
        //String str = "[!\"#$%&'()*+,-/:;<=>?@\\^_`{|}~]";
        //scanner = new Scanner(stream).useDelimiter("\\p{javaWhitespace}+|" + str + "+");
        scanner = new Scanner(stream).useDelimiter("");
        numberPattern = numberPattern();
    }

    public void rmSpace(){
        scanner.findWithinHorizon("\\s+", 0);
    }

    public String getString() throws EOFException, IOException {
        try {
            String text = scanner.findWithinHorizon("\\w+", 0);
            if (text == null){
                throw new EOFException();
            }
            return text;
        } catch (NoSuchElementException e) {
            throw new EOFException(e.getLocalizedMessage());
        }
    }

    public Character getChar() throws EOFException, IOException {
        try {
            String text = scanner.findWithinHorizon(".", 0);
            if (text == null){
                throw new EOFException();
            }
            return text.charAt(0);
        } catch (NoSuchElementException e) {
            throw new EOFException(e.getLocalizedMessage());
        }
    }

    public Number getNumber() throws EOFException, IOException {
        try {
            //return scanner.nextBigDecimal();
            String text = scanner.findWithinHorizon(numberPattern, 0);
            if (text == null){
                throw new EOFException();
            }
            return new BigDecimal(text);
        } catch (NumberFormatException e){//InputMismatchException e) {
            throw new IOException(e);
        } catch (NoSuchElementException e) {
            throw new EOFException(String.valueOf(e));
        }
    }

    @Override
    public Object getLVal() {
        return lastValue;
    }

    @Override
    public int yylex() throws IOException {
        int c = EOF;

        try {
            rmSpace();

            if (scanner.hasNext(numberPattern)) {
                try {
                    lastValue = getNumber();
                } catch (IOException ex) {
                    throw new IOException("can get number after position " + scanner.match().end(), ex);
                }
                return NUM;
            }

            if (scanner.hasNext()) {
                try {
                    return getChar();
                } catch (IOException ex) {
                    throw new IOException("can get char after position " + scanner.match().end(), ex);
                }
            } 

        } catch (EOFException eof) {
            return EOF;
        }

        return c;
    }

    @Override
    public void yyerror(String s) {
        System.err.println(s);
    }

    public final Pattern numberPattern() {
        String groupSeparator = "\\,";
        String decimalSeparator = "\\.";
        String nanString = "NaN";
        String infinityString = "Infinity";
        String positivePrefix = "";
        String negativePrefix = "\\-";
        String positiveSuffix = "";
        String negativeSuffix = "";
        String non0Digit = "[\\p{javaDigit}&&[^0]]";
        String str = ""; //"[-+]?"

        // \\p{javaDigit} may not be perfect, see above
        String digit = "([0-9]|(\\p{javaDigit}))";
        String exponent = "([eE]" + str + digit + "+)?";
        String groupedNumeral = "(" + non0Digit + digit + "?" + digit + "?("
                + groupSeparator + digit + digit + digit + ")+)";

        // Once again digit++ is used for performance, as above
        String numeral = "((" + digit + "++)|" + groupedNumeral + ")";
        String decimalNumeral = "(" + numeral + "|" + numeral + decimalSeparator
                + digit + "*+|" + decimalSeparator + digit + "++)";
        String nonNumber = "(NaN|" + nanString + "|Infinity|" + infinityString + ")";

        String positiveFloat = "(" + positivePrefix + decimalNumeral
                + positiveSuffix + exponent + ")";
        String negativeFloat = "(" + negativePrefix + decimalNumeral
                + negativeSuffix + exponent + ")";

        String decimal = "((" + str + decimalNumeral + exponent + ")|"
                + positiveFloat + "|" + negativeFloat + ")";
        String hexFloat = str + "0[xX][0-9a-fA-F]*\\.[0-9a-fA-F]+([pP][-+]?[0-9]+)?";

        String positiveNonNumber = "(" + positivePrefix + nonNumber + positiveSuffix + ")";
        String negativeNonNumber = "(" + negativePrefix + nonNumber + negativeSuffix + ")";

        String signedNonNumber = "((" + str + nonNumber + ")|"
                + positiveNonNumber + "|"
                + negativeNonNumber + ")";

        return Pattern.compile("((\\d+)\\.(\\d+)|(\\d+))");//lets be easier ...
                //decimal + "|" + hexFloat + "|" + signedNonNumber);
    }
}
